#include "UGLFont.h"
#include "GL/glu.h"

#include <sstream>

#include <boost/algorithm/string.hpp>

using namespace std;

string ClipBoard= "";
// Contains several fixes thanks to the nice people from http://www.cs.northwestern.edu/~sco590/fonts_tutorial.html
// Thank you!

/********************************** Private functions **********************************/

/* This Function Gets The First Power Of 2 >= The
 * Int That We Pass It.
 */
inline int TGLFont::next_power_of_2( int a ) {
	int rval=2;
	while ( rval<a ) rval<<=1;
	return rval;
}

/* Create A Display List Corresponding To The Given Character.
 */
void TGLFont::make_dlist( FT_Face face, uint8 ch, GLuint list_base, GLuint * tex_base ) {
	if ( FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT ) )
		throw std::runtime_error( "FT_Load_Glyph failed" );

	FT_Glyph glyph;
	if ( FT_Get_Glyph( face->glyph, &glyph ) )
		throw std::runtime_error( "FT_Get_Glyph failed" );

	FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
	FT_BitmapGlyph bitmap_glyph = ( FT_BitmapGlyph )glyph;

	FT_Bitmap& bitmap=bitmap_glyph->bitmap;

	int width = next_power_of_2( bitmap.width );
	int height = next_power_of_2( bitmap.rows );

	GLubyte* expanded_data = new GLubyte[ 2 * width * height];

	for ( int j=0; j < height;j++ ) for ( int i=0; i < width; i++ ) {
			expanded_data[2*( i+j*width )] = 255;
			expanded_data[2*( i+j*width )+1] = ( i>=bitmap.width || j>=bitmap.rows ) ? 0 : bitmap.buffer[i + bitmap.width*j];
		}

	glBindTexture( GL_TEXTURE_2D, tex_base[ch] );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data );

	delete [] expanded_data;
	glNewList( list_base+ch,GL_COMPILE );
		glBindTexture(GL_TEXTURE_2D, tex_base[ch]);
		glPushMatrix();
		glTranslatef( bitmap_glyph->left,0,0 );

		glTranslatef( 0,bitmap_glyph->top-bitmap.rows,0 );

		float   x=( float )bitmap.width / ( float )width,
				y=( float )bitmap.rows  / ( float )height;

		glBegin( GL_QUADS );
		glTexCoord2f( 0,0 ); glVertex2i( 0,bitmap.rows );
		glTexCoord2f( 0,y ); glVertex2i( 0,0 );
		glTexCoord2f( x,y ); glVertex2i( bitmap.width,0 );
		glTexCoord2f( x,0 ); glVertex2i( bitmap.width,bitmap.rows );
		glEnd();
		glPopMatrix();
		glTranslatef( face->glyph->advance.x >> 6 ,0,0 );
	glEndList();

	CharWidths[ch] = face->glyph->face->glyph->advance.x >> 6;
	FT_Done_Glyph( glyph );
}

/* A Fairly Straightforward Function That Pushes
 * A Projection Matrix That Will Make Object World
 * Coordinates Identical To Window Coordinates.
 */
inline void TGLFont::pushScreenCoordinateMatrix() {
	glPushAttrib( GL_TRANSFORM_BIT );
	GLint   viewport[4];
	glGetIntegerv( GL_VIEWPORT, viewport );
	glMatrixMode( GL_PROJECTION );
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D( viewport[0],viewport[2],viewport[1],viewport[3] );
	glPopAttrib();
}

/* Pops The Projection Matrix Without Changing The Current
 * MatrixMode.
 */
inline void TGLFont::pop_projection_matrix() {
	glPushAttrib( GL_TRANSFORM_BIT );
	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
	glPopAttrib();
}

TGLFont::TGLFont( const char * fname, uint h, uint32 attrs) {
	printf("creating font '%s' h=%i a=%i\n",fname, h, attrs);
	//textures = new GLuint[256];
	this->height=h;

	FT_Library library;
	if ( FT_Init_FreeType( &library ) )
		throw std::runtime_error( "FT_Init_FreeType failed" );

	FT_Face face;

	if ( FT_New_Face( library, fname, attrs, &face ) )
		throw std::runtime_error( "FT_New_Face failed (there is probably a problem with your font file)" );

	FT_Set_Char_Size( face, h << 6, h << 6, 96, 96 );
	FT_Matrix     mat={0x10000,0,0,-0x10000};
	FT_Set_Transform( face, /* target face object */  &mat, NULL); /* pointer to 2d vector */
	list_base=glGenLists( 128 );
	glGenTextures( 128, textures );

	for ( unsigned char i=0;i<128;i++ )
		make_dlist( face,i,list_base,textures );

	FT_Done_Face( face );

	FT_Done_FreeType( library );

	EllipsisWidth = TextWidth(ellipsis);
}

/********************************** Public Functions **********************************/

TGLFont::~TGLFont() {
	printf("destroying font\n");
	glDeleteLists( list_base,128 );
	glDeleteTextures( 128,textures );
	//delete [] textures;
}

void TGLFont::drawText( int x, int y, const char *text)
{
	//FIXME: 8000 is always larger then ScreenWidth, but this is an ugly hax
	drawText(TRectangle(x, y, 8000, 8000), TP_Left, TP_Top, text);
}

void TGLFont::drawText( const int x, const int y, const string &text )
{
	drawText(x, y, text.c_str() );
}

void TGLFont::drawText(const TRectangle& Rect, const HTextParams& HParams, const VTextParams& VParams, const char *text)
{
	GLuint font=list_base;
	float h=height/.63f;

	const char *start_line=text;
	vector<string> lines;

	boost::split(lines, text, boost::is_any_of("\n"));

	glPushAttrib( GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT );
	glMatrixMode( GL_MODELVIEW );
	glDisable( GL_LIGHTING );
	glEnable( GL_TEXTURE_2D );
	glDisable( GL_DEPTH_TEST );
	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	glListBase( font );

	float modelview_matrix[16];
	glGetFloatv( GL_MODELVIEW_MATRIX, modelview_matrix );

	for ( uint i=0;i<lines.size();i++ )
	{
		string str = lines[i];
		int TWidth	= TextWidth(str);
		int Left	= Rect.Left;
		int Top		= Rect.Top;
		int Right	= Rect.Right ;
		int Bottom	= Rect.Bottom;

		if (HParams == TP_Right)
		{
			Left = max(Left, Right - TWidth);
		}
		else
		if (HParams == TP_HCenter)
		{
			Left = max(Left, (Right+Left- TWidth)/2);
		}

		if (VParams ==TP_Bottom)
		{
			Top = max(Top, (int)(Bottom - (lines.size()-i)*h));
		}
		else
		if (VParams ==TP_VCenter)
		{
			Top = max(Top, (int)((Top+Bottom- h)/2 - (lines.size()-i-1)*h));
		}

		if (TWidth > Right-Left)
		{
			if (EllipsisWidth > Right-Left)
			{
				str="";
			}
			else
			{

				if (HParams==TP_Right) //Ellipsis in the left
				{
					int WidthTill = 0;
					size_t j = str.length();
					for (; j>0 && WidthTill+CharWidths[str[j]]+EllipsisWidth < Right-Left; j--)
					WidthTill = WidthTill+CharWidths[str[j]];
					str.erase(0, j);
					str = ellipsis+str;
				}
				else
				{
					int WidthTill = 0;
					size_t j = 0;
					for (; j<str.length() && WidthTill+CharWidths[str[j]]+EllipsisWidth < Right-Left; j++)
					WidthTill = WidthTill+CharWidths[str[j]];
					str.erase(j, str.length()-j);
					str = str+ ellipsis;
				}
			}
		}

		glPushMatrix();
		glLoadIdentity();
		glTranslatef( Left, Top + h*(i+1) ,0 );
		glMultMatrixf( modelview_matrix );
		glCallLists( str.length(), GL_UNSIGNED_BYTE, str.c_str() );
		glPopMatrix();
	}

	glPopAttrib();
}

float TGLFont::Height() {
	return( height/0.63 );
}

int TGLFont::TextWidth(const string& Text ) {
	int result = 0;
	for ( uint i=0; i<Text.size(); i++ ) {
		result+=CharWidths[Text[i]];
	}
	return result;
}

int TGLFont::getCharWidth(const uint8 chr)
{
	return CharWidths[chr];
}

TGLFontRef::TGLFontRef(std::string name, uint size, uint32 attrs)
{
	// map containing weak references to loaded fonts
	static map<std::string, TGLFontWeakRef> refmap;
	// default name
	if (name == "") name = "Data/Fonts/Vera/Vera.ttf";

	// build up id string used to uniquely identify fonts
	stringstream ss;
	ss << name << "///" << size << "///" << attrs;
	string id = ss.str();

	// check if this font has been loaded before
	TGLFontWeakRef weakref(refmap[id]);
	// try to get a non-weak reference
	TGLFontSharedRef cref = weakref.lock();
	// if that fails, it means this font isnt already loaded
	if (cref == NULL) {
		// so load it
		TGLFontSharedRef nref(new TGLFont(name.c_str(), size, attrs));
		cref = nref;
		weakref = TGLFontWeakRef(cref);
		refmap[id] = weakref;
	}
	internal_reference = cref;
}
